<template>
	<div>
		<!-- K 线 -->
		<VChart ref="charts" class="chart" :loading="showLoading" />
	</div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import VChart, { THEME_KEY } from 'vue-echarts'
import * as echarts from 'echarts'

// 设置黑暗模式主题
// provide(THEME_KEY, 'dark')

const downColor = '#00da3c'
const upColor = '#ec0000'
const charts = ref<echarts.ECharts>()
const showLoading = ref<boolean>(true)
// SSR:true 正常工作
// SSR:false 时 mounted 时不会触发监控, 增加 { immediate: true } 进行配置(避免数据一样而不更新)
const bars: Bar[] = []

const instrument = useCookie('instrument', { default: () => 'rb2405' })

// 订阅实时行情
watch(instrument, async (newVal, oldVal) => {
	showLoading.value = true
	await readBar()
	if (oldVal.length > 0) {
		useHttp.post('/unsubscript', { instrument_id: [oldVal] })
	}
	useHttp.post('/subscript', { instrument_id: [newVal] })
})

function splitData(rawData: Bar[]): KLine {
	let categoryData = []
	let values = []
	let volumes = []
	for (let i = 0; i < rawData.length; i++) {
		categoryData.push(rawData[i].DateTime)
		values.push([rawData[i].Open, rawData[i].Close, rawData[i].Low, rawData[i].High])
		volumes.push([i, rawData[i].Volume, rawData[i].Close >= rawData[i].Open ? 1 : -1])
	}

	return {
		categoryData: categoryData,
		values: values,
		volumes: volumes,
	}
}

function calculateMA(dayCount: number, values: Bar[]) {
	var result = []
	for (var i = 0, len = values.length; i < len; i++) {
		if (i < dayCount) {
			result.push('-')
			continue
		}
		var sum = 0
		for (var j = 0; j < dayCount; j++) {
			sum += values[i - j].Close
		}
		result.push(+(sum / dayCount).toFixed(3))
	}
	return result
}
let klines = ref<KLine>({ categoryData: [], values: [], volumes: [] } as KLine)

const option = ref({
	animation: true, // 动画
	legend: {
		top: 10,
		left: 'center',
		data: ['K', 'MA5', 'MA10', 'MA20', 'MA30'],
		formatter: (name: string) => {
			return name === 'K' ? instrument.value : name // K 线显示合约
		},
	},
	tooltip: {
		trigger: 'axis',
		axisPointer: {
			type: 'cross',
		},
		borderWidth: 1,
		borderColor: '#ccc',
		padding: 10,
		textStyle: {
			color: '#000',
		},
		position: (pos: any, params: any, el: any, elRect: any, size: any) => {
			const obj: Record<string, number> = {
				top: 10,
			}
			obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 30
			return obj
		},
		formatter: (params: any) => {
			let result = ''
			let content = ''
			params.map((item: any, index: number) => {
				if (item.data.empty) {
					result = ''
				} else {
					let val
					if (item.seriesType === 'candlestick') {
						const bar = item.value as number[]
						content += `<span style='font-size:12px'>${item.marker}开</span><span style='font-size:12px; float:right'>` + bar[1].toFixed(3) + `</span><br/>`
						content += `<span style='font-size:12px'>${item.marker}高</span><span style='font-size:12px; float:right'>` + bar[2].toFixed(3) + `</span><br/>`
						content += `<span style='font-size:12px'>${item.marker}低</span><span style='font-size:12px; float:right'>` + bar[3].toFixed(3) + `</span><br/>`
						content += `<span style='font-size:12px'>${item.marker}收</span><span style='font-size:12px; float:right'>` + bar[4].toFixed(3) + `</span><br/>`
						result = `<span style='font-size: 14px;'>${item.name}</span> <br/> ${content}`
					} else {
						if (item.seriesName === 'Volume') val = parseFloat(item.value[1])
						else val = Number(item.value).toFixed(3)
						content += `<span style='font-size:12px'>${item.marker} ${item.seriesName === 'Volume' ? '量' : item.seriesName}</span><span style='font-size:11px; float:right'>` + val + `</span><br/>`
						result = `<span style='font-size: 14px;'>${item.name}</span> <br/> ${content}`
					}
				}
			})
			return result
		},
		extraCssText: 'width: 130px',
	},
	axisPointer: {
		link: [
			{
				xAxisIndex: 'all',
			},
		],
		label: {
			backgroundColor: '#777',
		},
	},
	toolbox: {
		feature: {
			dataZoom: {
				yAxisIndex: false,
			},
			brush: {
				type: ['lineX', 'clear'],
			},
		},
	},
	brush: {
		xAxisIndex: 'all',
		brushLink: 'all',
		outOfBrush: {
			colorAlpha: 0.1,
		},
	},
	// Volume 颜色
	visualMap: {
		type: 'piecewise',
		show: false,
		seriesIndex: 1,
		dimension: 2,
		pieces: [
			{
				value: 1,
				color: upColor,
			},
			{
				value: -1,
				color: downColor,
			},
		],
	},
	grid: [
		{
			left: '3%',
			right: '8%',
			height: '50%',
		},
		{
			left: '3%',
			right: '8%',
			top: '63%',
			height: '16%',
		},
	],
	xAxis: [
		{
			type: 'category',
			data: klines.value.categoryData,
			gridIndex: 0,
			// boundaryGap: false, // 默认:左右留出半格
			axisLine: { onZero: false, show: false },
			// splitLine: { show: true },
			axisTick: { show: false }, // 不显示X轴的小竖
			axisLabel: { show: false }, // 不显示X坐标值
			min: 'dataMin',
			max: 'dataMax',
			axisPointer: {
				z: 100,
				// 日期显示在副图,主图不显示
				label: {
					show: false,
				},
			},
		},
		{
			type: 'category',
			data: klines.value.categoryData,
			gridIndex: 1,
			// boundaryGap: false, // 左右留出半格
			axisLine: { onZero: false },
			axisTick: { show: false },
			splitLine: { show: false },
			axisLabel: {
				align: 'left',
				formatter: (value: any) => {
					return value.slice(0, 16)
				},
				show: true,
			}, // 显示坐标值
			min: 'dataMin',
			max: 'dataMax',
		},
	],
	yAxis: [
		{
			scale: true,
			position: 'right',
			gridIndex: 0,
			splitArea: {
				show: true,
			},
		},
		{
			scale: true,
			position: 'right',
			gridIndex: 1,
			splitNumber: 4,
			// axisLabel: { show: true },
			// axisLine: { show: true },
			// axisTick: { show: true },
			// splitLine: { show: true },
			splitArea: {
				show: true,
			},
		},
	],
	dataZoom: [
		{
			type: 'inside',
			xAxisIndex: [0, 1], // 控制 xAxis 中指定的数据
			// 图1不赋值, 图2赋值,会被认为'可选', 才能够被删除
			// start: 90,
			// end: 100,
			// rangeMode: ['percent', 'percent'],
		},
		{
			show: true,
			xAxisIndex: [0, 1],
			type: 'slider',
			top: '85%',
			start: 90,
			end: 100,
			// rangeMode: ['percent', 'percent'],
		},
	],
	series: [
		{
			name: 'K',
			type: 'candlestick',
			data: klines.value.values,
			itemStyle: {
				color: upColor,
				color0: downColor,
				borderColor: undefined,
				borderColor0: undefined,
			},
			markPoint: {
				label: {
					formatter: (param: any) => {
						return param != null ? Math.round(param.value) + '' : ''
					},
				},
				// data: [
				// 	{
				// 		name: '最高',
				// 		type: 'max',
				// 		valueDim: 'High',
				// 	},
				// 	{
				// 		name: '最低',
				// 		type: 'min',
				// 		valueDim: 'Low',
				// 	},
				// 	{
				// 		name: '均价',
				// 		type: 'average',
				// 		valueDim: 'Close',
				// 	},
				// ],
			},
		},
		{
			name: 'Volume',
			type: 'bar',
			data: [] as number[][],
			xAxisIndex: 1, // 副图
			yAxisIndex: 1,
		},
		{
			name: 'MA5',
			type: 'line',
			xAxisIndex: 0,
			symbol: 'none', // 'emptyCircle'(默认) 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
			data: [] as (string | number)[],
			smooth: true,
			lineStyle: {
				opacity: 0.5,
			},
		},
		{
			name: 'MA10',
			type: 'line',
			data: [] as (string | number)[],
			symbol: 'none',
			smooth: true,
			lineStyle: {
				opacity: 0.5,
			},
		},
		{
			name: 'MA20',
			type: 'line',
			data: [] as (string | number)[],
			symbol: 'none',
			smooth: true,
			lineStyle: {
				opacity: 0.5,
			},
		},
		{
			name: 'MA30',
			type: 'line',
			data: [] as (string | number)[],
			symbol: 'none',
			smooth: true,
			lineStyle: {
				opacity: 0.5,
			},
		},
	],
	// animationDuration: 0, //这里两个动画设置可以让图表更顺滑
	// animationEasing: 'cubicInOut', //这里两个动画设置可以让图表更顺滑
})

const readBar = async () => {
	// 用 instrument 时, 合约变化会触发一次查询, 再加上 readBar 的调用, 就重复执行了
	await useHttp.get<Bar[]>('/history', { instrument_id: instrument.value }, null, 'Order By date_time').then(({ data }) => {
		bars.splice(0, bars.length) // remove

		if (data.value && data.value.length > 0) {
			data.value.forEach((x) => {
				x.UpDown = x.Close >= x.Open ? 1 : -1
				bars.push(x)
			})
		}
	})
	await useHttp.get<Bar[]>('/latest', { instrument_id: instrument.value }).then(({ data }) => {
		if (data.value && data.value.length > 0) {
			data.value.forEach((x) => {
				x.UpDown = x.Close >= x.Open ? 1 : -1
				bars.push(x)
			})
		}
	})

	// 显示 100个 K线
	option.value.dataZoom.forEach((x) => {
		x.start = (1 - 100 / bars.length) * 100
		x.end = 100
	})

	// 采用 data 属性赋值的方式, 以解决 build 后不显示K线的问题
	klines.value = splitData(bars)
	option.value.xAxis.forEach((x) => (x.data = klines.value.categoryData))
	option.value.series.forEach((x) => {
		if (x.name === 'K') x.data = klines.value.values
		else if (x.name === 'Volume') x.data = klines.value.volumes
		else if (x.name === 'MA5') {
			x.data = calculateMA(5, bars)
			bars.forEach((b, i) => (b.MA5 = x.data[i] as string | number))
		} else if (x.name === 'MA10') {
			x.data = calculateMA(10, bars)
			bars.forEach((b, i) => (b.MA10 = x.data[i] as string | number))
		} else if (x.name === 'MA20') {
			x.data = calculateMA(20, bars)
			bars.forEach((b, i) => (b.MA20 = x.data[i] as string | number))
		} else if (x.name === 'MA30') {
			x.data = calculateMA(30, bars)
			bars.forEach((b, i) => (b.MA30 = x.data[i] as string | number))
		}
	})

	option.value.animation = true // 动画
	showLoading.value = false
	// 第2个参数用 true 不合并
	charts.value?.setOption(option.value, true, true)
	// ===  为实时更新准备  ===
	// 取消  zoom 设置, 避免后续刷新时恢复到原来的大小(会导致,zoom变为0~100)
	option.value.dataZoom.forEach((x) => {
		delete x.start
		delete x.end
	})
	option.value.animation = false // 动画
}

// 计算最后一个 Bar 的均值
const calculateMALast = (dayCount: number, values: Bar[]) => {
	if (values.length === dayCount) {
		let sum = 0
		// 达到K线数据
		for (let i = 0; i < dayCount; i++) {
			sum += values[i].Close
		}
		return sum / dayCount
	} else if (values.length > dayCount) {
		const bar = values.at(-2)
		const curBar = values.at(-1)
		const startBar = values.at(-1 - dayCount)
		if (bar && startBar && curBar) {
			const val = new Map(Object.entries(bar)).get('MA' + dayCount) as number
			return (val * dayCount - startBar.Close + curBar.Close) / dayCount
		}
	}
	return '-'
}

const { data: barSSE } = useSSE('bar')
watch(barSSE, (val) => {
	const bar = JSON.parse(val as string) as Bar
	if (bars) {
		const curBar = bars.at(-1)
		if (curBar) {
			if (bar.InstrumentID === curBar.InstrumentID) {
				bar.UpDown = bar.Close >= bar.Open ? 1 : -1
				if (bar.DateTime === curBar.DateTime) {
					// 同K线
					bars[bars.length - 1] = bar
					bar.MA5 = calculateMALast(5, bars)
					bar.MA10 = calculateMALast(10, bars)
					bar.MA20 = calculateMALast(20, bars)
					bar.MA30 = calculateMALast(30, bars)

					option.value.xAxis.forEach((x) => (x.data[-1] = bar.DateTime))
					option.value.series.forEach((x) => {
						// 'Open', 'Close', 'Low', 'High', 'Volume'
						if (x.name === 'K') x.data[-1] = [bar.Open, bar.Close, bar.Low, bar.High]
						else if (x.name === 'Volume') (x.data as number[][])[-1] = [bars.length - 1, bar.Volume, bar.UpDown as number]
						else if (x.name === 'MA5') (x.data as any[])[-1] = bar.MA5
						else if (x.name === 'MA10') (x.data as any[])[-1] = bar.MA10
						else if (x.name === 'MA20') (x.data as any[])[-1] = bar.MA20
						else if (x.name === 'MA30') (x.data as any[])[-1] = bar.MA30
					})
				} else {
					bars.push(bar)
					bar.MA5 = calculateMALast(5, bars)
					bar.MA10 = calculateMALast(10, bars)
					bar.MA20 = calculateMALast(20, bars)
					bar.MA30 = calculateMALast(30, bars)

					option.value.xAxis.forEach((x) => x.data.push(bar.DateTime))
					option.value.series.forEach((x) => {
						// 'Open', 'Close', 'Low', 'High', 'Volume'
						if (x.name === 'K') (x.data as any[]).push([bar.Open, bar.Close, bar.Low, bar.High])
						else if (x.name === 'Volume') (x.data as number[][]).push([bars.length - 1, bar.Volume, bar.UpDown as number])
						else if (x.name === 'MA5') (x.data as any[]).push(bar.MA5)
						else if (x.name === 'MA10') (x.data as any[]).push(bar.MA10)
						else if (x.name === 'MA20') (x.data as any[]).push(bar.MA20)
						else if (x.name === 'MA30') (x.data as any[]).push(bar.MA30)
					})
				}

				// 第2个参数, NotMarge 为 true时, 导致刷新数据时闪烁
				charts.value?.setOption(option.value, false, true)
			}
		}
	}
})

onMounted(async () => {
	await nextTick().then(() => {
		if (charts.value) {
			// refreshBar() SSR:true时会自动触发
			if (instrument.value) {
				readBar()
				// 订阅实时行情,页面刷新时不会触发 watch(instrument)
				if (instrument.value) useHttp.post('/subscript', { instrument_id: [instrument.value] })
			}
		}
	})
})
</script>
<style scoped>
.chart {
	height: 500px;
}
.demo-date-picker {
	display: flex;
	width: 100%;
	padding: 0;
	flex-wrap: wrap;
}

.demo-date-picker .block {
	padding: 30px 0;
	text-align: center;
	border-right: solid 1px var(--el-border-color);
	flex: 1;
}

.demo-date-picker .block:last-child {
	border-right: none;
}

.demo-date-picker .demonstration {
	display: block;
	color: var(--el-text-color-secondary);
	font-size: 14px;
	margin-bottom: 20px;
}
</style>
